首先了解物件導向(OOP)有兩種類型,有基於類別(class-based)以及基於原型(prototype-based)兩種,JavaScript 屬於後者 - 基於原型的語言。
使用 new
建立新的物件時,所執行的函式稱為建構式,用來初始化物件以及建立初始化屬性。
const Person = function(name){
this.name = name;
this.sayHi = function(){
console.log("Hi")
}
}
// new 一個 instance
const peter = new Person("Peter")
了解建構式之後我們來看上面的程式碼有哪些問題:
這邊的 Person
有個 sayHi
的函式,於是我再 new
一個人出來。
const alice = new Person("Alice")
然後比較兩個人的 sayHi()
後發現:
console.log(peter.sayHi === alice.sayHi) // false
回傳 false
代表這兩個 function
明明都是同個方法,但各占了不同的記憶體,所以這樣明顯不好。
那要怎麼辦? 我們請 prototype
出來幫我們解決。
承接上面的問題,我們改在 Person.prototype
中新增方法。
const Person = function(name){
this.name = name;
}
Person.prototype.sayHi = function(){
console.log("Hi")
}
同樣的,new
出來的 instance 都有 sayHi 這個方法,這時候我們再比較一次:
console.log(peter.sayHi === alice.sayHi) // true
在 JavaScript 中,每個物件都有一個原型,物件可以從原型上繼承方法。
這就是為什麼剛剛 const alice = new Person("alice")
之後,明明 alice 沒有 syaHi 這個函式卻還能使用,因為它會從 Person.prototype 去找。
而連接 alice 與 Person.prototype 靠的就是 proto。
也就是說 alice.proto 會指向 Person.prototype。
當 Person.prototype 找不到時,就繼續找 Person.prototype.proto,直到找不到回傳 null
。而透過這個不斷串起來的鏈,就叫做原型鏈。
這也是為什麼我們的 array 可以直接使用 filter、forEach 等方法,我們將陣列的 proto 印出來看看:
const arr = [];
console.log(arr.__proto__)
console.log(arr.__proto__.__proto__) //null
ES6 中新增了 class
供我們使用,也可以使用繼承。不過 class 只是語法糖,這點不要搞混了。
以下實際建立一個 class:
class Animal {
constructor(name) {
this.name = name;
}
eat(){
console.log("我愛吃飼料");
}
}
const dog = new Animal("dog");
上面的 eat()
會放到 Animal.prototype
。
不過如果有使用 private
、protected
、abstract
等需求的話,只能使用 TypeScript 來撰寫。